/* 
 * A664p7_packet.c -- Implements packet handling functions
 *
 *
 *------------------------------------------------------------------------------
 *
 *                    ---   PROPRIETARY NOTICE   ---
 *
 *   The information contained herein is proprietary to Smiths Aerospace.
 *   and shall not be reproduced or disclosed in whole or in part or used
 *   for any design or manufacture except when such user possesses direct
 *   written authorization from Smiths Aerospace.
 *
 *   Copyright 2006, Smiths Aerospace.
 *
 *------------------------------------------------------------------------------
 *
 * modification history
 * --------------------
 * 30 mar 06	Huizenga	Initial revision.
 * 09 may 06	Huizenga	Added additional comments
 * 09 may 06	Huizenga	Updated functions to return the standardized success/fail constants
 * 02 jun 06	Huizenga	Fixed size of rm seq. num (16 bit -> 8 bit)
 * 05 jun 06	Huizenga	Fixed setup_packet_pointers to use p->data_length instead of p->uh->length
 *								to calculate rm_seq_num pointer
 * 05 jun 06	Huizenga    fixed read_A664p7_packet to correctly compute data_length for messages < 17 bytes
 * 05 jun 06	Huizenga    modified create_packet to always allocate min. 17 bytes of payload space
 */


#include "pcap.h"

#ifndef WIN32
	#include <sys/socket.h>
	#include <netinet/in.h>
#else
	#include <winsock.h>
#endif
#include "A664p7_internal.h"

/* 
 * convert_packet_header_byte_order:
 *   After a packet has been created, this can be used to set the byte order of the entire packet.
 *   The current packet header byte ordering is tracked in the p->packet_header_byte_order variable.
 *   The current packet header byte ordering is not user available, the user should just call
 *   convert_packet_header_byte_order before any section of code requiring a specific byte order.
 * INPUTS:  p -- The packet to be modified
 *      order -- The desired order, either PACKET_ORDER_HOST or PACKET_ORDER_NET    
 * RETURNS:  A664P7_FAIL on failure, A664P7_SUCC on success
 */
int convert_packet_header_byte_order(PACKET *p, int order)
{
	/* Warning: (!p) MUST be checked BEFORE the (!p->xxxxx) entries .. do NOT change*/
	if((!p) || (!p->eh) || (!p->ih) || (!p->uh) || (!p->rm_seq_num))
		return A664P7_FAIL;

	if((p->packet_header_byte_order == PACKET_ORDER_HOST) && (order == PACKET_ORDER_NET))
	{
		eh_to_net(p->eh);
		ih_to_net(p->ih);
		uh_to_net(p->uh);
		p->packet_header_byte_order = PACKET_ORDER_NET;
	}
	else if((p->packet_header_byte_order == PACKET_ORDER_NET) && (order == PACKET_ORDER_HOST))
	{
		eh_from_net(p->eh);
		ih_from_net(p->ih);
		uh_from_net(p->uh);
		p->packet_header_byte_order = PACKET_ORDER_HOST;
	}
	return A664P7_SUCC;
}

/* setup_packet_header_pointers:
 * Sets up only the packet header pointers .. this is used on new packets, since they won't yet
 *	have information about the length of the data that setup_packet_pointers requires 
 * INPUTS: p -- A pointer to the PACKET structure to be modified 
 * RETURNS: A664P7_FAIL on fail, A664P7_SUCC on success
 */
int setup_packet_header_pointers(PACKET *p)
{
	if(!p || !p->buf)
		return A664P7_FAIL;

	p->eh = (ETHERNET_HEADER*)p->buf;
	p->ih = (IP_HEADER*)(p->buf + sizeof(ETHERNET_HEADER));
	p->uh = (UDP_HEADER*)(p->buf + sizeof(ETHERNET_HEADER) + sizeof(IP_HEADER));
	p->data = p->buf + sizeof(ETHERNET_HEADER) + sizeof(IP_HEADER) + sizeof(UDP_HEADER);
	return A664P7_SUCC;
}

/* setup_packet_pointers:
 *     Sets the buffer pointers.  Will fail on non-IP / non-UDP packets.  
 *  INPUTS: p -- A pointer to a PACKET structure which contains a valid packet in its data buffer
 *  RETURNS: A664P7_FAIL on fail, A664P7_SUCC on success
 */
int setup_packet_pointers(PACKET *p)
{
	int packet_order = 0;
	
	if(!p)
		return A664P7_FAIL;
	packet_order = p->packet_header_byte_order;

	/* Take care of the ethernet header */
	p->eh = (ETHERNET_HEADER*)p->buf;

	/* convert EH to host order */
	if(packet_order == PACKET_ORDER_NET)
		eh_from_net(p->eh);

	/* We only know what to do with IP packets, give up if it's something else */
	if(p->eh->type != ETH_TYPE_IP)
	{
		/* Put the ordering back the way it was, if we changed it */
		if(packet_order == PACKET_ORDER_NET)
			eh_to_net(p->eh);
		return A664P7_FAIL;
	}

	/* Take care of the IP header */
	p->ih = (IP_HEADER*)(p->buf + sizeof(ETHERNET_HEADER));
	if(packet_order == PACKET_ORDER_NET)
		ih_from_net(p->ih);

	/* We only know how to handle UDP packets, give up if it's something else */
	if(p->ih->protocol != PROT_UDP)
	{
		/* Put the ordering back the way it was, if we changed it */
		if(packet_order == PACKET_ORDER_NET)
		{	
			eh_to_net(p->eh);
			ih_to_net(p->ih);
		}
		return A664P7_FAIL;
	}

	/* Take care of the UDP header */
	p->uh = (UDP_HEADER*)(p->buf + sizeof(ETHERNET_HEADER) + sizeof(IP_HEADER));
	if(packet_order == PACKET_ORDER_NET)
			uh_from_net(p->uh);

	/* Take care of the data pointer */
	p->data = p->buf + sizeof(ETHERNET_HEADER) + sizeof(IP_HEADER) + sizeof(UDP_HEADER);

	/* Finally, take care of the Redundancy Management sequency number */
	if(p->data_length)
	{
		p->rm_seq_num = (unsigned char *)(((char*)p->data) + p->data_length);
	}

	/* At this point, all headers & the seq. num are in host order, */
	/* So if the packet was originally in net order, we need to convert it back */
	if(packet_order == PACKET_ORDER_NET)
	{
		eh_to_net(p->eh);
		ih_to_net(p->ih);
		uh_to_net(p->uh);
	}
	return A664P7_SUCC;
}

/* destroy_packet:
 *     Frees memory used by a PACKET structure and its members.  
 *  INPUTS: p -- A pointer to a PACKET structure that is to be freed
 *  RETURNS: A664P7_FAIL on fail, A664P7_SUCC on success
 */
int destroy_packet(PACKET *pkt)
{
	if(!pkt)
		return A664P7_FAIL;

	if(pkt->buf)
		free(pkt->buf);

	free(pkt);
	return A664P7_SUCC;
}

/* create_packet:
 *     Allocates and initializes a packet and its members.  
 *  INPUTS: max_data_length -- Maximum size (in bytes) of messages that can be contained within the packet
 *  RETURNS: A pointer to the newly created packet structure, or NULL on failure
 */
PACKET  *create_packet(int max_data_length)
{
	PACKET *p;
	
	if(max_data_length < MIN_A664P7_MESSAGE_SIZE)
		max_data_length = MIN_A664P7_MESSAGE_SIZE;

	/* Attempt to allocate packet structure */
	if(!(p = calloc(sizeof(PACKET),1) ))
		return NULL;

	/* Attempt to allocate data buffer */
	if(!(p->buf = calloc(sizeof(ETHERNET_HEADER) + sizeof(IP_HEADER) + sizeof(UDP_HEADER) + max_data_length + RM_SEQ_NUM_LEN, 1)))
	{
		free(p);
		return NULL;
	}

	setup_packet_header_pointers(p);

	/* set the rm seq num pointer to point just after the data area */
    p->rm_seq_num = (unsigned char *)(((char*)p->uh) + sizeof(UDP_HEADER) + max_data_length);

	initialize_ethernet_header(p->eh);
	initialize_ip_header(p->ih);
	initialize_udp_header(p->uh);

	p->buffer_length = sizeof(ETHERNET_HEADER) + sizeof(IP_HEADER) + sizeof(UDP_HEADER) + max_data_length + RM_SEQ_NUM_LEN;
	p->data_buffer_length = max_data_length;

	/* No data in the buffer at creation, but need MIN_A664P7_MESSAGE_SIZE bytes of padding to get 60 byte frame */
	p->data_length = MIN_A664P7_MESSAGE_SIZE;
	p->packet_header_byte_order = PACKET_ORDER_NET;
	return p;
}

/* copy_packet:
 *     Copys one existing, valid packet to another.  
 *  INPUTS: dest   -- Destination (target) packet
 *          source -- Source packet
 *  RETURNS: A664P7_FAIL on fail, A664P7_SUCC on success
 */
int copy_packet(PACKET *dest, PACKET *source)
{
	if((!dest) || (!source))
		return A664P7_FAIL;

	/* Check if there is enough space in *dest's data buffer */
	if(dest->data_buffer_length < source->data_length)
		return A664P7_FAIL;

	/* Zero out any unused buffer */
	if(dest->buffer_length > source->buffer_length)
		memset(dest->buf, 0, dest->buffer_length);

	/* Copy the buffer */
	memcpy(dest->buf, source->buf, (dest->buffer_length < source->buffer_length)?dest->buffer_length:source->buffer_length);

	/* Copy metadata */
	dest->data_length = source->data_length;;
	dest->packet_header_byte_order = source->packet_header_byte_order;

	/* Initialize the pointers on the destination packet */
	setup_packet_pointers(dest);
	return A664P7_SUCC;
}

/* send_A664p7_packet:
 *     Converts input packet to network order, generates a checksum, and transmits.  
 *  INPUTS: p	-- PACKET structure to be transmitted
 *
 *  RETURNS: A664P7_FAIL on fail, A664P7_SUCC on success
 */
int send_A664p7_packet(PACKET *p, pcap_t *device)
{
	int size;

	/* Warning: (!p) MUST be checked BEFORE the (!p->xxxxx) entries .. do NOT change*/
	if((!p) || (!device) || (!p->ih) || (!p->uh) || (!p->eh) || (!p->data) || (!p->buf) || (!p->rm_seq_num))
		return A664P7_FAIL;

	size = sizeof(ETHERNET_HEADER) + 
								sizeof(IP_HEADER) + 
								sizeof(UDP_HEADER) + 
								p->data_length + RM_SEQ_NUM_LEN; 		// size

	/* Fragmentation could be done here */

	convert_packet_header_byte_order(p, PACKET_ORDER_NET);

	p->ih->check = generate_ip_checksum(p->ih);

	if(pcap_sendpacket(device,	p->buf,	size) == -1)
		return A664P7_FAIL;

	return A664P7_SUCC;
}

/* read_A664p7_packet:
 *     Reads a packet (including non-A664P7) from the network interface into an
 *	   internal, static PACKET structure.  This PACKET structure's buffer pointer
 *     is set point to the internal buffer returned by libpcap.  The packet
 *     returned by read_A664p7_packet is only valid until the next time that the
 *     function is called.
 *
 *  *************  NOTE:  This function is NOT reentrant! *******************
 *
 *  INPUTS: *device -- The network interface on which to read the packet
 *
 *  RETURNS: NULL on fail, a pointer to the last-read packet on success
 */
PACKET *read_A664p7_packet(pcap_t *device)
{
	struct pcap_pkthdr *header;
	static PACKET p;
    u_char *data;

	if(pcap_next_ex(device, &header, &data)==1)
	{

		p.buf = data;
		/* Buffer is provided by libpcap, so we set the packet length parameters
			according to the buffer that we were given */
		p.buffer_length = header->caplen;

		/* Calculate the size of the payload recieved on wire (NOT necc. equal to the size
			of the UDP payload -- this size INCLUDES padding (anything after the UDP payload
			and before the sequence number is assumed to be padding) */
		p.data_buffer_length = header->caplen - sizeof(ETHERNET_HEADER) - 
								sizeof(IP_HEADER) -
								sizeof(UDP_HEADER) - 
								 RM_SEQ_NUM_LEN;

		if(p.data_buffer_length < 0) 
			p.data_buffer_length = 0;

		/* On recieved messages, the size of the data buffer is always equal to the amount
		 * of data buffer actually in use (since libpcap automatically allocates it for us */
		p.data_length = p.data_buffer_length;


		p.packet_header_byte_order = PACKET_ORDER_NET;
		/* setup pointers, convert to host order */
		setup_packet_pointers(&p);
		convert_packet_header_byte_order(&p, PACKET_ORDER_HOST);

    	/* If it's not IP, we're all done, because we can't read other protocol types */
		if(p.eh->type != ETH_TYPE_IP)
		{
			printf("Received non-IP packet, ignoring..\n");
			return 0;
		}

		/* If it's not UDP protocol, we're all done, because we can't read other protocol types */
		if( p.ih->protocol != PROT_UDP)
		{
			printf("Received non-UDP packet, ignoring..\n");
			return 0;
		}

		/* Fragment could be queued for later use here */
		/* Only if a new fragment has been queued, reassembly could be attempted here */

		/* p->uh->length is contained in the rx'd packet data .. use it
			to figure out the length of the data buffer*/

		return &p;
	}
	return 0;
}
